#include "Assembly.h"
#include "MaterialLib.h"

Assembly::Assembly():
		m_scale(1.0f, 1.0f, 1.0f),
		m_DL_id(glGenLists(2)), m_cDL_id(m_DL_id + 1),
		m_DL_mod(true), m_cDL_mod(true),
		m_name("group"), m_matname("default"),
		m_v_mod(true), m_m_mod(true),
		m_depth(0), m_parent(NULL)
{
#ifndef VALGRIND
	if (!m_DL_id || !m_cDL_id)
		throw runtime_error("couldn't allocate DisplayLists");
#endif
}

Assembly::Assembly(const Vertex& pos):
		m_position(pos), m_scale(1.0f, 1.0f, 1.0f),
		m_DL_id(glGenLists(2)), m_cDL_id(m_DL_id + 1),
		m_DL_mod(true), m_cDL_mod(true),
		m_name("group"), m_matname("default"),
		m_v_mod(true), m_m_mod(true),
		m_depth(0), m_parent(NULL)
{
#ifndef VALGRIND
	if (!m_DL_id || !m_cDL_id)
		throw runtime_error("couldn't allocate DisplayLists");
#endif
}

Assembly::Assembly(float x, float y, float z):
		m_position(x, y, z), m_scale(1.0f, 1.0f, 1.0f),
		m_DL_id(glGenLists(2)), m_cDL_id(m_DL_id + 1),
		m_DL_mod(true), m_cDL_mod(true),
		m_name("group"), m_matname("default"),
		m_v_mod(true), m_m_mod(true),
		m_depth(0), m_parent(NULL)
{
#ifndef VALGRIND
	if (!m_DL_id || !m_cDL_id)
		throw runtime_error("couldn't allocate DisplayLists");
#endif
}

Assembly::Assembly(const Vertex& pos, const Vertex& rot):
		m_position(pos), m_rotation(rot), m_scale(1.0f, 1.0f, 1.0f),
		m_DL_id(glGenLists(2)), m_cDL_id(m_DL_id + 1),
		m_DL_mod(true), m_cDL_mod(true),
		m_name("group"), m_matname("default"),
		m_v_mod(true), m_m_mod(true),
		m_depth(0), m_parent(NULL)
{
#ifndef VALGRIND
	if (!m_DL_id || !m_cDL_id)
		throw runtime_error("couldn't allocate DisplayLists");
#endif
}

Assembly::Assembly(float x, float y, float z, float a, float b, float g):
		m_position(x, y, z), m_rotation(a, b, g), m_scale(1.0f, 1.0f, 1.0f),
		m_DL_id(glGenLists(2)), m_cDL_id(m_DL_id + 1),
		m_DL_mod(true), m_cDL_mod(true),
		m_name("group"), m_matname("default"),
		m_v_mod(true), m_m_mod(true),
		m_depth(0), m_parent(NULL)
{
#ifndef VALGRIND
	if (!m_DL_id || !m_cDL_id)
		throw runtime_error("couldn't allocate DisplayLists");
#endif
}


Assembly::~Assembly()
{
	for (list<Face*>::iterator it = m_face.begin(); it != m_face.end(); ++it)
	{
		delete *it;
	}
	for (list<Assembly*>::iterator it = m_parts.begin(); it != m_parts.end(); ++it)
	{
		delete *it;
	}
	glDeleteLists(m_DL_id, 2);
}


const Vertex& Assembly::position() const
{
	return m_position;
}

Vertex& Assembly::position()
{
	if (NULL != m_parent)
	{
		m_parent->m_cDL_mod = true;
	}
	
	return m_position;
}

const Vertex& Assembly::rotation() const
{
	return m_rotation;
}

Vertex& Assembly::rotation()
{
	if (NULL != m_parent)
	{
		m_parent->m_cDL_mod = true;
	}
	
	return m_rotation;
}

const Vertex& Assembly::scale() const
{
	return m_scale;
}

Vertex& Assembly::scale()
{
	if (NULL != m_parent)
	{
		m_parent->m_cDL_mod = true;
	}
	
	return m_scale;
}


void Assembly::setDepth(Assembly* child, unsigned depth)
{
	child->m_depth = depth;
	
	for (list<Assembly*>::iterator it = child->m_parts.begin(); it != child->m_parts.end(); ++it)
	{
		setDepth(*it, depth + 1);
	}
}

void Assembly::addPart(Assembly* child)
{
	child->m_parent = this;
	setDepth(child, m_depth + 1);
	m_parts.push_back(child);
	m_cDL_mod = true;
}

void Assembly::addFace(Face* face)
{
	m_face.push_back(face);
}

void Assembly::print(Assembly* ptr)
{
	string placeholder(ptr->m_depth, '\t');
	cout << placeholder << ptr->m_name << " @ " << ptr->m_depth
	     << ", " << m_matname << ":" << endl;
	cout << placeholder << ptr->m_position.toString() << endl;
	cout << placeholder << ptr->m_rotation.toString() << endl;
	for (list<Assembly*>::iterator it = ptr->m_parts.begin(); it != ptr->m_parts.end(); ++it)
	{
		print(*it);
	}
}

void Assembly::draw()
{
	cout << "A::draw() " << m_name << " @ " << m_depth << endl;
	
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	{
		float x, y, z;
		m_position.getAll(x, y, z);
		glTranslatef(x, y, z);
		
		m_rotation.getAll(x, y, z);
		glRotatef(x, 1.0f, 0.0f, 0.0f);
		glRotatef(y, 0.0f, 1.0f, 0.0f);
		glRotatef(z, 0.0f, 0.0f, 1.0f);
		
		if (0 != m_depth)
		{
			glCallList(m_DL_id);
			glCallList(m_cDL_id);
		}
		else
		{
			update_DL_rec(this);
			print(this);
			glCallList(m_DL_id);
			glCallList(m_cDL_id);
		}
	}
	glPopMatrix();
}

void Assembly::draw_me()
{
	string act_matname = m_matname;
	
	// set object's material
	MaterialLib::mat_citer tmp = MaterialLib::getInstance()->get(m_matname);
	if (tmp != MaterialLib::getInstance()->end())
	{
		tmp->second.set();
	}
	
	// draw object's faces (if needed, set their material)
	for (list<Face*>::const_iterator it = m_face.begin(); it != m_face.end(); ++it)
	{
		const string& face_mat = (*it)->material();
		if (face_mat != act_matname)
		{
			MaterialLib::mat_citer tmp = MaterialLib::getInstance()->get(m_matname);
			if (tmp != MaterialLib::getInstance()->end())
			{
				// if face's material is different, set it
				tmp->second.set();
			}
		}
		(*it)->draw();
	}
}

void Assembly::update_DL()
{
	cout << "Updating DL for " << m_name << endl;
	if (m_v_mod)
	{
		update_vertex();
	}
	
	if (m_m_mod)
	{
		update_material();
	}
	
	glNewList(m_DL_id, GL_COMPILE);
	{
		draw_me();
	}
	glEndList();
	m_DL_mod = false;
}

void Assembly::draw_children()
{
	for (list<Assembly*>::iterator it = m_parts.begin(); it != m_parts.end(); ++it)
	{
		(*it)->draw();
	}
}

void Assembly::update_cDL()
{
	cout << "Updating CDL of " << m_name << endl;
	glNewList(m_cDL_id, GL_COMPILE);
	{
		draw_children();
	}
	glEndList();
	m_cDL_mod = false;
}

void Assembly::update_DL_rec(Assembly* ptr)
{
	if (ptr->m_DL_mod  &&  0 != ptr->m_depth)
	{
		ptr->update_DL();
	}
	
	if (ptr->m_cDL_mod)
	{
		ptr->update_cDL();
	}
	
	for (list<Assembly*>::iterator it = ptr->m_parts.begin(); it != ptr->m_parts.end(); ++it)
	{
		update_DL_rec(*it);
	}
}

string& Assembly::name()
{
	return m_name;
}

const string& Assembly::name() const
{
	return m_name;
}

const string& Assembly::material() const
{
	return m_matname;
}

string& Assembly::material()
{
	m_m_mod = true;
	return m_matname;
}

void Assembly::update_material()
{
	for (list<Face*>::iterator it = m_face.begin(); it != m_face.end(); ++it)
	{
		(*it)->material() = m_matname;
	}
	m_m_mod = false;
}

void Assembly::update_vertex()
{
	// groups have no own Vertex
	m_v_mod = false;
}

void Assembly::exportObj(ostream& ss, ostream& ss_mat)
{
	ss_mat << *MaterialLib::getInstance() << endl;
	
	Matrix4D tsr(m_position, m_scale, m_rotation);
	// vertex and normal counters
	int vc = 1, nc = 1;
	
	ss << "o " << m_name << endl;
	dump_me(ss, tsr, vc, nc);
	
	for (list<Assembly*>::const_iterator it = m_parts.begin(); it != m_parts.end(); ++it)
	{
		ss << endl << "o " << (*it)->m_name << endl;
		(*it)->exportObj(ss, tsr, vc, nc);
		ss << endl;
	}
}

void Assembly::exportObj(ostream& ss, const Matrix4D& parent_tsr, int& vc, int& nc)
{
	Matrix4D tsr(parent_tsr), my_translation(m_position, m_scale, m_rotation);
//	cout << "I'm " << m_name << ", parent has tsr:" << endl;
//	parent_tsr.print();
//	cout << "my tsr: " << endl;
//	my_translation.print();
	tsr *= my_translation;
//	cout << "result tsr:" << endl;
//	tsr.print();

	dump_me(ss, tsr, vc, nc);
	
	for (list<Assembly*>::const_iterator it = m_parts.begin(); it != m_parts.end(); ++it)
	{
		ss << endl << "o " << (*it)->m_name << endl;
		(*it)->exportObj(ss, tsr, vc, nc);
	}
}


void Assembly::dump_me(ostream& ss, const Matrix4D& parent_tsr, int& vc, int& nc)
{
	if (m_v_mod)
	{
		update_vertex();
	}
	
	if (m_m_mod)
	{
		update_material();
	}
	
	map<const Vertex*, int> vmap, nmap;
	
	string act_matname = m_matname;
	if (m_face.begin() != m_face.end())
	{
		ss << "usemtl " << m_matname << endl;
	}
	for (list<Face*>::const_iterator it = m_face.begin(); it != m_face.end(); ++it)
	{
		const string& face_mat = (*it)->material();
		if (face_mat != act_matname)
		{
			act_matname = face_mat;
			ss << "usemtl " << act_matname << endl;
		}
		ss << (*it)->toString(parent_tsr, vmap, nmap, vc, nc);
	}
}

